package prefuse.data.column;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.logging.Logger;

import prefuse.data.DataReadOnlyException;
import prefuse.data.DataTypeException;

/**
 * Column implementation for storing arbitrary Object values.
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class ObjectColumn extends AbstractColumn {

    private Object[] m_values;
    private int m_size;

    /**
     * Create a new empty ObjectColumn. The type is assumed to be Object.
     */
    public ObjectColumn() {
        this(Object.class);
    }

    /**
     * Create a new ObjectColumn.
     * @param type the data type of Objects in this column 
     */
    public ObjectColumn(Class type) {
        this(type, 0, 10, null);
    }

    /**
     * Create a new ObjectColumn. The type is assumed to be Object. 
     * @param nrows the initial size of the column
     */
    public ObjectColumn(int nrows) {
        this(Object.class, nrows, nrows, null);
    }

    /**
     * Create a new ObjectColumn.
     * @param type the data type of Objects in this column 
     * @param nrows the initial size of the column
     */
    public ObjectColumn(Class type, int nrows) {
        this(type, nrows, nrows, null);
    }

    /**
     * Create a new ObjectColumn.
     * @param type the data type of Objects in this column 
     * @param nrows the initial size of the column
     * @param capacity the initial capacity of the column
     * @param defaultValue the default value for the column. If this value
     * is cloneable, it will be cloned when assigned as defaultValue, otherwise
     * the input reference will be used for every default value.
     */
    public ObjectColumn(Class type, int nrows, int capacity, Object defaultValue) {
        super(type, defaultValue);
        if (capacity < nrows) {
            throw new IllegalArgumentException("Capacity value can not be less than the row count.");
        }
        m_values = new Object[capacity];
        try {
            // since Object's clone method is protected, we default to
            // using reflection to create clones.
            Cloneable def = (Cloneable) defaultValue;
            Method m = def.getClass().getMethod("clone", (Class[]) null);
            for (int i = 0; i < capacity; ++i) {
                m_values[i] = m.invoke(m_defaultValue, (Object[]) null);
            }
        } catch (Exception e) {
            if (defaultValue != null) {
                Logger.getLogger(getClass().getName()).fine("Default value of type \"" + defaultValue.getClass().getName() + "\" is not " + "cloneable. Using Object reference directly.");
            }
            Arrays.fill(m_values, defaultValue);
        }
        m_size = nrows;
    }

    // ------------------------------------------------------------------------
    // Column Metadata

    /**
     * @see prefuse.data.column.Column#getRowCount()
     */
    public int getRowCount() {
        return m_size;
    }

    /**
     * @see prefuse.data.column.Column#setMaximumRow(int)
     */
    public void setMaximumRow(int nrows) {
        if (nrows > m_values.length) {
            int capacity = Math.max((3 * m_values.length) / 2 + 1, nrows);
            Object[] values = new Object[capacity];
            System.arraycopy(m_values, 0, values, 0, m_size);
            try {
                // since Object's clone method is protected, we default to
                // using reflection to create clones.
                Cloneable def = (Cloneable) m_defaultValue;
                Method m = def.getClass().getMethod("clone", (Class[]) null);
                for (int i = m_size; i < capacity; ++i) {
                    values[i] = m.invoke(m_defaultValue, (Object[]) null);
                }
            } catch (Exception e) {
                Arrays.fill(values, m_size, capacity, m_defaultValue);
            }
            m_values = values;
        }
        m_size = nrows;
    }

    // ------------------------------------------------------------------------
    // Data Access Methods

    public void revertToDefault(int row) {
        try {
            // since Object's clone method is protected, we default to
            // using reflection to create clones.
            Cloneable def = (Cloneable) m_defaultValue;
            Method m = def.getClass().getMethod("clone", (Class[]) null);
            set(m.invoke(m_defaultValue, (Object[]) null), row);
        } catch (Exception e) {
            set(m_defaultValue, row);
        }
    }

    /**
     * Get the data value at the specified row
     * @param row the row from which to retrieve the value
     * @return the data value
     */
    public Object get(int row) {
        if (row < 0 || row > m_size) {
            throw new IllegalArgumentException("Row index out of bounds: " + row);
        }
        return m_values[row];
    }

    /**
     * Set the data value at the specified row
     * @param val the value to set
     * @param row the row at which to set the value
     */
    public void set(Object val, int row) {
        if (m_readOnly) {
            throw new DataReadOnlyException();
        } else if (row < 0 || row > m_size) {
            throw new IllegalArgumentException("Row index out of bounds: " + row);
        } else if (val == null || canSet(val.getClass())) {
            // get the previous value
            Object prev = m_values[row];

            // exit early if no change
            // do we trust .equals() here? for now, no.
            if (prev == val)
                return;

            // set the new value
            m_values[row] = val;

            // fire a change event
            fireColumnEvent(row, prev);
        } else {
            throw new DataTypeException(val.getClass());
        }
    }

} // end of class ObjectColumn
